package com.wh1200.mybatis.xmlless.model

import com.wh1200.kotlin.only
import com.wh1200.mybatis.xmlless.constant.Strings.LINE_BREAK_INDENT
import com.wh1200.mybatis.xmlless.constant.TRIM_CONDITION_CONNECTOR
import com.wh1200.mybatis.xmlless.model.base.BaseCriteria
import com.wh1200.mybatis.xmlless.model.criteria.CriteriaParameter
import com.wh1200.mybatis.xmlless.util.ScriptUtils
import com.wh1200.mybatis.xmlless.util.indent
import com.wh1200.mybatis.xmlless.util.trim


/**
 * Created by 吴昊 on 2018/12/20.
 */
open class QueryCriteriaGroup(
    val criterion: List<BaseCriteria>,
    methodInfo: MethodInfo,
    private val testInfo: TestInfo? = null,
    append: ConditionConnector = ConditionConnector.AND
) : BaseCriteria(listOf(), methodInfo, append) {

  companion object {
    const val CONDITION_GROUP_TEMPLATE = "(\n%s\n)"
  }

  init {
    assert(criterion.isNotEmpty())
  }

  fun isEmpty(): Boolean {
    return criterion.isEmpty()
  }

  fun isNotEmpty(): Boolean {
    return criterion.isNotEmpty()
  }

  override fun getTests(parameterTest: TestInfo?): String {
    val list = criterion.map {
      it.getTests(parameterTest)
    }.distinct().filter { it.isNotBlank() }
    if (list.size == 1) {
      return list.only()
    }
    return list.joinToString(" or ") { "($it)" }
  }

  override fun toSqlWithoutTest(): String {
    return when {
      criterion.size > 1 -> createConditionGroupSql()
      else               -> criterion.first().toSqlWithoutTest()
    }
  }

  override fun getTableNames(): List<String> {
    return criterion.map {
      it.getTableNames()
    }.flatten().distinct()
  }

  override fun getColumns(extraJoins: List<JoinInfo>): List<SelectColumn> {
    return criterion.map { it.getColumns() }.flatten()
  }

  override fun getProperty(): String {
    return ""
  }

  override fun toSql(parameterTest: TestInfo?, ifTag: String): String {
    return toSql()
  }

  override fun toSql(): String {
    return when {
      criterion.size > 1 -> {
        createConditionGroupSql()
      }

      else               -> {
        criterion.first().toSql(testInfo, getIfTag())
      }
    }
  }

  override fun clone(parameters: List<CriteriaParameter>): BaseCriteria {
    return QueryCriteriaGroup(criterion, methodInfo, testInfo)
  }

  override fun clone(append: ConditionConnector): BaseCriteria {
    return QueryCriteriaGroup(criterion, methodInfo, testInfo, append)
  }

  override fun toString(): String {
    return toSql()
  }

  open fun getIfTag(): String {
    return "if"
  }

  private fun createConditionGroupSql(): String {
    val test = this.getTests(testInfo)
    val expression = trimCondition(criterion.joinToString(LINE_BREAK_INDENT) {
      it.toSql()
    }.trim())
    val sql = (CONDITION_GROUP_TEMPLATE.format(
        expression.indent()
    ) + " \n " + append.toString()).indent()
    return if (test.isNotBlank()) {
      ScriptUtils.convertIf(sql, test, true, getIfTag())
    } else {
      sql
    }
  }

  private fun trimCondition(sql: String): String {
    if (sql.endsWith(" AND")) {
      return sql.trim(" AND")
    }
    if (sql.endsWith(" OR")) {
      return sql.trim(" OR")
    }
    return TRIM_CONDITION_CONNECTOR.format(sql)
  }

}
